package sudoku.controller;

import java.util.LinkedList;

import language.Messages;
import sudoku.model.Model;
import sudoku.view.FirstGui;
import sudoku.view.IView;
import sudoku.view.PreferenciasGui;
import sudoku.view.SecondGui;
import utils.Constants;

/**
 * Clase que se encarga de controlar las acciones de cada uno de los botones de
 * las gui.
 * 
 * @author karma-ELSE
 * 
 */
public class GuiController implements IController
{

	private int										difficulty_												= Constants.FACIL;
	private IView									previousView_;
	private IView									view_;
	private Model									model_;
	private boolean								areOpenedDifferentViewsOfSudoku_	= false;
	private boolean								canLoadObserver_									= true;
	private static GuiController	instance													= null;

	/**
	 * @return la unica instancia creada de GuiController
	 */
	public static GuiController getInstance()
	{
		if (instance == null) {
			instance = new GuiController();
		}
		return instance;
	}

	/**
	 * inicializa el modelo
	 */
	private GuiController() {
		model_ = new Model();
	}

	@Override
	public void setView(IView view)
	{
		this.view_ = view;
	}

	@Override
	public void process(String model)
	{
		model = model.trim();
		if (canLoadObserver_) {
			model_.loadObserver(view_);
			canLoadObserver_ = false;
		}

		if (model.compareTo(Messages.getString("Gui.nuevo_juego")) == 0) { //$NON-NLS-1$
			// Empieza un nuevo juego
			newGame();
		}

		if (model.compareTo(Messages.getString("Gui.detener")) == 0) { //$NON-NLS-1$
			// Detiene la partida actual
			stopGame();
		}
		
		if (model.compareTo(Messages.getString("Gui.preferencias")) == 0) { //$NON-NLS-1$
			// Maneja las preferencias del juego
			preferences();
		}

		if (model.compareTo(Messages.getString("Gui.resolver")) == 0) { //$NON-NLS-1$
			solve();
		}

		if (model.compareTo(Messages.getString("Gui.borrar")) == 0) { //$NON-NLS-1$
			// reinicia el juego actual
			deleteMovements();
		}

		if (model.compareTo(Messages.getString("Gui.pista")) == 0) { //$NON-NLS-1$
			// Ofrece una pista al jugador
			provideClue();
		}

		if (model.compareTo(Messages.getString("Gui.cambiar_vista")) == 0) { //$NON-NLS-1$
			// Intercambia interfaces
			swapGui();
		}

		if (model.compareTo(Messages.getString("Gui.acerca_de")) == 0) { //$NON-NLS-1$
			// Menu Ayuda sobre las reglas del juego
			view_.present(Messages.getString("GuiController.ayuda"));
		}

		if (model.compareTo(Messages.getString("Gui.salir")) == 0) { //$NON-NLS-1$
			// sale de la aplicacion
			exit();
		}

		if (model.compareTo(Messages.getString("Gui.cancelar")) == 0) { //$NON-NLS-1$
			// Cancelar perteneciente a PREFERENCIAS
			view_.dispose();
			areOpenedDifferentViewsOfSudoku_ = false;
			PreferenciasGui.setInstanceNull();
			view_ = previousView_;// retorno a la vista que estaba
		}
		// aceptar perteneciente a PREFERENCIAS
		if (model.compareTo(Messages.getString("Gui.aceptar")) == 0) { //$NON-NLS-1$
			runPreferencias((PreferenciasGui) view_);
			areOpenedDifferentViewsOfSudoku_ = false;
			PreferenciasGui.setInstanceNull();
		}

		if (model.compareTo(Messages.getString("Gui.topten")) == 0) { //$NON-NLS-1$
			// muestra el top-ten por la view que este en juego
			showTopTen();
		}

		if (model.charAt(0) == '|') {
			// model : Trae lo ingresado por usuario en el juego como string
			// manejo de input sobre la matriz de la view
			acceptedKeys(model);
		}
	}

	/**
	 * Inicia un nuevo juego, primero verificando que sea posible en cuanto al entorno que
	 * se encuentra
	 */
	private void newGame()
	{
		if (this.gameStopped())
			model_.newGame(difficulty_);
		else
			view_.present(Messages.getString("GuiController.falta_cerrar_2")); //$NON-NLS-1$
	}

	/**
	 * Abre la ventana de preferencias si es posible
	 */
	private void preferences()
	{
		if (!model_.isRunningGame()) {// si no se esta jugando
			previousView_ = view_;
			PreferenciasGui.getIntance();// singleton de preferencias
			areOpenedDifferentViewsOfSudoku_ = true;
		} else
			view_.present(Messages.getString("GuiController.detener_partida")); //$NON-NLS-1$
	}

	/**
	 * Muestra el top-ten por pantalla
	 */
	private void showTopTen()
	{
		if (this.gameStopped()) {
			LinkedList<String> lista = model_.getTopTen(this.difficulty_);
			String s = Messages.getString("GuiController.nombre_puntaje"); //$NON-NLS-1$
			int listSize = lista.size();
			for (int i = listSize - 1; i >= 0; --i)
				s = s + (listSize - i) + ": " + lista.get(i) + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
			view_.present(s);
		} else
			view_.present(Messages.getString("GuiController.falta_cerrar_1")); //$NON-NLS-1$
	}

	/**
	 * Genera una pista de ser posible, de lo contrario, termina el juego, ya que
	 * este habrá sido finalizado
	 */
	private void provideClue()
	{
		if (model_.isRunningGame()) {
			if (!model_.generateClue()) // generateClue retorna true ssi pudo ingresar
																	// una pista
				view_.present(Messages.getString("GuiController.solicitar_pista")); //$NON-NLS-1$
			if (model_.isSolvedSudoku())
				this.stopGame();
		}
	}

	/**
	 * Muestra la solución del juego y luego lo termina (lo corta)
	 */
	private void solve()
	{
		if (model_.isRunningGame()) {
			model_.setTruePressedSolved();
			model_.endGame();
			view_.present(Messages.getString("Gui.solucion")); //$NON-NLS-1$
			((sudoku.view.AbstractView) view_).blockBoard(); // bloquea la matriz para que no pueda ingresar ningun valor
		} else
			view_.present(Messages.getString("GuiController.opcion_no_valida")); //$NON-NLS-1$
	}

	/**
	 * Restaura el tablero al que éste era en el principio del juego
	 */
	private void deleteMovements()
	{
		if (model_.isRunningGame()) {
			model_.restoreBoard();
		} else
			view_.present(Messages.getString("GuiController.no_inicio_juego")); //$NON-NLS-1$
	}

	/**
	 * Detiene el juego actual
	 */
	private void stopGame()
	{
		if (model_.isRunningGame()) {
			model_.setTruePressedStop();// avisa que se presiono stop
			model_.endGame();
			view_.present(Messages.getString("GuiController.juego_detenido")); //$NON-NLS-1$
			((sudoku.view.AbstractView) view_).blockBoard();
		}
	}

	/**
	 * Sale del juego cuando no hay ninguna otra ventana abierta
	 */
	private void exit()
	{
		if (this.gameStopped()) {
			view_.dispose();
			model_.saveChanges();
		} else
			view_.present(Messages.getString("GuiController.falta_cerrar_0")); //$NON-NLS-1$
	}

	/**
	 * Intercambia las interfaces graficas
	 */
	private void swapGui()
	{ // si no esta abierta preferencias
		if (!areOpenedDifferentViewsOfSudoku_) {
			previousView_ = view_;
			if (view_.getClass().getCanonicalName().toString()
					.compareTo(Constants.VIEW2) == 0)
				// si la vista actual es la view2, entonces inicializo la view1
				new FirstGui();
			else {
				if (view_.getClass().getCanonicalName().toString()
						.compareTo(Constants.VIEW1) == 0)
					// si la vista actual es la view1, entonces inicializo la view2
					new SecondGui();
			}
			previousView_.dispose();
			model_.loadObserver(view_);
			if (model_.isRunningGame()) {
				// dibuja el tablero inicial del juego
				model_.notifyDraw();
				
				// dibuja el tablero como estaba hasta el momento que el usuario decidio cambiar de vista
				for (int i = 0; i < 9; ++i)
					for (int j = 0; j < 9; ++j)
						((sudoku.view.AbstractView) view_).setNumberInSudoku(i, j,
								model_.getBoardPos(i, j), Constants.TECLA_INGRESADA);
			}
		} else
			view_.present(Messages
					.getString("GuiController.no_se_puede_cambiar_inerfaz")); //$NON-NLS-1$
	}

	/**
	 * @param prefView
	 *          Setea las preferencias en el modelo
	 */
	private void runPreferencias(PreferenciasGui prefView)
	{
		if (prefView.hardDifficulty()) {
			// modifico la dificultad de juego
			difficulty_ = Constants.DIFICIL;
			prefView.dispose();
			areOpenedDifferentViewsOfSudoku_ = false;
			view_ = previousView_;// retorno a la vista que estaba
		} else {
			if (prefView.mediumDifficulty()) {
				difficulty_ = Constants.MEDIANA;
				prefView.dispose();
				areOpenedDifferentViewsOfSudoku_ = false;
				view_ = previousView_;// retorno a la vista que estaba
			} else {
				if (prefView.easyDifficulty()) {
					difficulty_ = Constants.FACIL;
					prefView.dispose();
					areOpenedDifferentViewsOfSudoku_ = false;
					view_ = previousView_;// retorno a la vista que estaba
				} else {
					prefView.present(Messages
							.getString("GuiController.no_selecciono_dificultad")); //$NON-NLS-1$
				}
			}
		}
	}

	/**
	 * @param string
	 * @param position
	 * @return el entero correspondiente a la posicion position de una cadena de
	 *         la forma "|Int1|Int2,Int3"
	 */
	private Integer getValueInPos(String string, int position)
	{
		switch (position) {
		case 1:
			return Integer.parseInt(string.charAt(1) + ""); //$NON-NLS-1$		
		case 2:
			return Integer.parseInt(string.charAt(3) + ""); //$NON-NLS-1$
		default: // 3
			return Integer.parseInt(string.charAt(5) + ""); //$NON-NLS-1$
		}
	}

	/**
	 * Pide y luego guarda en el modelo el nombre del usuario que va a ingresar al top-ten
	 */
	private void getNameTopTen()
	{
		String message = Messages.getString("GuiController.nombre_topten"); //$NON-NLS-1$
		String playerName = ((sudoku.view.AbstractView) view_).askName(message);
		model_.setUsernameTopTen(playerName);
	}

	/**
	 * @return true ssi no se encuentra un juego en curso y no se encuentra
	 *         abierta otra ventana mas que la del sudoku
	 */
	private boolean gameStopped()
	{
		return !model_.isRunningGame() && !areOpenedDifferentViewsOfSudoku_;
	}

	// 
	/**
	 * @param model. model trae el numero y las posiciones i,j
	 */
	private void acceptedKeys(String model)
	{
		int posI = this.getValueInPos(model, 2);
		int posJ = this.getValueInPos(model, 3);
		int number = this.getValueInPos(model, 1);
		((sudoku.view.AbstractView) view_).setNumberInSudoku(posI, posJ, number,
				Constants.TECLA_INGRESADA);
		model_.setBoardPos(posI, posJ, number);

		// tablero correctamente o lo lleno pero incorrectamente

		if (!model_.hasEmptySpaces()) {
			if (model_.isSolvedSudoku()) {
				model_.endGame();
				if (model_.nameRequested()) {// si el usuario tiene que entrar en el top-ten
					view_.present(Messages.getString("GuiController.juego_finalizado_1") //$NON-NLS-1$
							+ model_.getMovementAmount());
					this.getNameTopTen();
					model_.saveInTopTen();
				} else
					// si el usuario no ingresa al top-ten
					view_.present(Messages.getString("GuiController.juego_finalizado_2") //$NON-NLS-1$
							+ model_.getMovementAmount());
				((sudoku.view.AbstractView) view_).blockBoard(); // vacia el sudoku y no permite que se edite
			} else
				view_.present(Messages.getString("GuiController.solucion_incorrecta")); //$NON-NLS-1$
		}
	}
}
